/*
 * BLLib.Windows.Forms.OptionDialog
 * Andy Tidball
 * 
 * Project: Black Lightning General Library
 * Copyright: GNU General Public License
 */

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using BLLib.Configuration;
using BLLib.Properties;

namespace BLLib.Windows.Forms {
	/// <summary>
	/// A dialog box that allows a user to edit the Options within a hierarchical collection of OptionSets.
	/// </summary>
	public class OptionDialog : Form {
		// member data
		private OptionSet               _RootSet           = null;  // the root OptionSet in the dialog
		private bool                    _ApplyingAll       = false; // true while the values of all Options are being updated
		private Dictionary<int, string> _IndexNames        = null;  // the name of each value index used in the Options being displayed
		private Dictionary<string, int> _NameIndices       = null;  // _IndexNames mapped in reverse
		private bool                    _ShowUninitialized = false; // whether or not to show uninitialized OptionSets in the dialog
		private bool                    _ShowEmpty         = false; // whether or not to show OptionSets with no Options
		private int                     _InitialValueIndex = -1;    // the value index that is initially selected in ddlOptionLevel
		private IComparer<OptionSet>    _Sorter            = null;  // the comparer to sort the displayed OptionSets with

		// member data - controls
		private Panel                pnlTop;
		private Label                lblOptionLevel;
		private ComboBox             ddlOptionLevel;
		private CheckBox             chbPropogate;
		private SplitContainer       splMain;
		private ToolStrip            tlsOptionSetTools;
		private ToolStripSplitButton btnNew;
		private ToolStripButton      btnDelete;
		private ToolStripSeparator   sep1;
		private ToolStripButton      btnUp;
		private ToolStripButton      btnDown;
		private TreeView             trvOptionSets;
		private Label                lblOptionSet;
		private PropertyGrid         prgOptions;
		private Button               btnOK;
		private Button               btnCancel;
		private Button               btnApply;

		/// <summary>
		/// Creates a new OptionDialog.
		/// </summary>
		public OptionDialog() {
			// setup member data
			_IndexNames = new Dictionary<int, string>();

			// initialize our controls
			InitializeComponent();
		}

		/// <summary>
		/// Gets or sets the OptionSet which is displayed at the root of the hierarchy.
		/// </summary>
		public OptionSet RootOptionSet {
			get {
				return _RootSet;
			}
			set {
				// unwire from our current set
				if (_RootSet != null) {
					WireEvents(_RootSet, false);
				}

				// store the value
				_RootSet = value;

				// wire to the new set
				if (_RootSet != null) {
					WireEvents(_RootSet, true);
				}

				// clear out our nodes, since they're probably not accurate anymore
				trvOptionSets.Nodes.Clear();
			}
		}

		/// <summary>
		/// Gets or sets the value index that is initially selected in the Option Level drop-down list.
		/// </summary>
		public int InitialValueIndex {
			get {
				return _InitialValueIndex;
			}
			set {
				_InitialValueIndex = value;
			}
		}

		/// <summary>
		/// Gets a mapping of value indices for OptionValues to names as they should be displayed in the dialog.  Any ValueIndex not mapped to a name will be unselectable for modification.
		/// </summary>
		public Dictionary<int, string> ValueIndexNames {
			get {
				return _IndexNames;
			}
		}

		/// <summary>
		/// Gets or sets whether to show the Option Level controls on top of the editor or hide them.
		/// </summary>
		public bool ShowOptionLevels {
			get {
				return pnlTop.Visible;
			}
			set {
				pnlTop.Visible = value;
				if (value) {
					splMain.Location = new Point(splMain.Left, splMain.Top+pnlTop.Height);
					splMain.Size     = new Size(splMain.Width, splMain.Height-pnlTop.Height);
				} else {
					splMain.Location = new Point(splMain.Left, splMain.Top-pnlTop.Height);
					splMain.Size     = new Size(splMain.Width, splMain.Height+pnlTop.Height);
				}
			}
		}

		/// <summary>
		/// Gets or sets whether or not OptionSets with uninitialized Options or child OptionSets will be displayed.
		/// </summary>
		public bool ShowUninitializedSets {
			get {
				return _ShowUninitialized;
			}
			set {
				_ShowUninitialized = value;
			}
		}

		/// <summary>
		/// Gets or sets whether or not to show OptionSets that don't have any Options or child OptionSets with Options.
		/// </summary>
		public bool ShowEmptySets {
			get {
				return _ShowEmpty;
			}
			set {
				_ShowEmpty = value;
			}
		}

		/// <summary>
		/// Gets or sets the image list used for icons in the dialog's tree view.
		/// </summary>
		public ImageList ImageList {
			get {
				return trvOptionSets.ImageList;
			}
			set {
				trvOptionSets.ImageList = value;
				btnNew.DropDown.ImageList = value;
			}
		}

		/// <summary>
		/// Gets or sets the Comparer to use to sort the displayed OptionSets in the editor.  OptionSetIndexSorter and OptionSetNameSorter are both ready-to-go.
		/// </summary>
		public IComparer<OptionSet> Sorter {
			get {
				return _Sorter;
			}
			set {
				_Sorter = value;
			}
		}

		/// <summary>
		/// Gets or sets whether or not editing of the OptionSet list will be allowed, and whether the appropriate controls will be displayed or not.
		/// </summary>
		public bool AllowOptionSetEditing {
			get {
				return tlsOptionSetTools.Visible;
			}
			set {
				tlsOptionSetTools.Visible = value;
			}
		}

		/// <summary>
		/// Adds a new type of OptionSet that can be created if AllowOptionSetEditing is enabled.
		/// </summary>
		/// <param name="Base">The OptionSet to clone when creating a new OptionSet of this type.</param>
		public void AddOptionSetType(OptionSet Base) {
			// create a matching menu item
			ToolStripMenuItem MI = new ToolStripMenuItem(Base.Name);
			MI.ImageIndex = Base.IconIndex;
			MI.Tag = Base;
			MI.Click += btnNew_Click;
			btnNew.DropDown.Items.Add(MI);

			// if this is the first one, make this option set the base for the button too
			if (btnNew.DropDown.Items.Count == 1) {
				btnNew.Tag = Base;
			}
		}

		/// <summary>
		/// Applies the new values for all Options displayed in the dialog.
		/// </summary>
		public void ApplyAllNewValues() {
			// if there's nothing to apply, return now
			if (!btnApply.Enabled) {
				return;
			}

			// apply the changes
			_ApplyingAll = true;
			_RootSet.ApplyNewValues();
			_ApplyingAll = false;
			prgOptions.Refresh();
			RefreshApplyEnabled();
		}

		/// <summary>
		/// Applies the new values for all Options displayed in the dialog.
		/// </summary>
		public void CancelAllNewValues() {
			// if there's nothing to apply, return now
			if (!btnApply.Enabled) {
				return;
			}

			// cancel the changes
			_ApplyingAll = true;
			_RootSet.CancelNewValues();
			_ApplyingAll = false;
			prgOptions.Refresh();

			// nothing to apply anymore
			btnApply.Enabled = false;
		}

		/// <summary>
		/// Refreshes the Option Levels and hierarchy of OptionSets being displayed.
		/// </summary>
		public void RefreshDisplay() {
			// make sure we have an option set
			if (_RootSet == null) {
				return;
			}

			// make sure we have value index names
			if (_IndexNames.Count == 0) {
				_IndexNames.Add(0, "Values");
				_InitialValueIndex = 0;
			}

			// save which node is selected and then clear the tree view
			string SelectedNodePath  = string.Empty;
			OptionSet SelectedOptionSet = null;
			if (trvOptionSets.Nodes.Count > 0) {
				if (trvOptionSets.SelectedNode != null) {
					SelectedNodePath  = trvOptionSets.SelectedNode.FullPath;
					SelectedOptionSet = trvOptionSets.SelectedNode.Tag as OptionSet;
				}
				trvOptionSets.Nodes.Clear();
			}

			// build a sorted list of value indices
			List<int> ValueIndices = new List<int>(_IndexNames.Keys);
			ValueIndices.Sort();

			// populate the value index drop-down list
			_NameIndices = new Dictionary<string, int>();
			ddlOptionLevel.BeginUpdate();
			ddlOptionLevel.Items.Clear();
			for (int i=ValueIndices.Count-1; i >= 0; --i) {
				// find the index and value here
				int ValueIndex = ValueIndices[i];
				string ValueName  = _IndexNames[ValueIndex];

				// add the reverse mapping
				_NameIndices.Add(ValueName, ValueIndex);

				// add the item to the drop down list
				ddlOptionLevel.Items.Add(ValueName);

				// if this index is the one to initially select, do that
				if (ValueIndex == _InitialValueIndex) {
					ddlOptionLevel.SelectedIndex = ddlOptionLevel.Items.Count - 1;
				}
			}
			ddlOptionLevel.EndUpdate();

			// select the lowest option level available if they didn't specify one
			if (_InitialValueIndex < 0) {
				ddlOptionLevel.SelectedIndex = ddlOptionLevel.Items.Count - 1;
			}

			// start updating the tree view
			trvOptionSets.BeginUpdate();

			// add the root node to the tree view, which recursively builds the other nodes
			trvOptionSets.Nodes.Add(BuildNode(_RootSet));

			// expand all the nodes
			trvOptionSets.ExpandAll();

			// select the saved node
			if (SelectedNodePath == string.Empty) {
				// selected the root node
				trvOptionSets.SelectedNode = trvOptionSets.TopNode;
			} else {
				// dig down to find the saved node
				string[] Names = SelectedNodePath.Split(new string[] { trvOptionSets.PathSeparator }, StringSplitOptions.None);
				TreeNode CurrentNode = trvOptionSets.TopNode;
				for (int i=0; i<Names.Length; ++i) {
					string Name = Names[i];

					// look for the current node's sibling that matches this name
					while (CurrentNode != null && CurrentNode.Text != Name && CurrentNode.Tag != SelectedOptionSet) {
						CurrentNode = CurrentNode.NextNode;
					}

					// check if we found it or ran out of possibilities
					if (CurrentNode == null) {
						// the name didn't match anything on this level, so we've reached as far as we can
						break;
					} else {
						// if we're not at the end of the list yet, move down to the next level
						if (i < Names.Length-1) {
							CurrentNode = CurrentNode.FirstNode;
						}
					}
				}

				// if we found a node, select it
				if (CurrentNode != null) {
					trvOptionSets.SelectedNode = CurrentNode;
				}
			}

			// we're done updating the tree view
			trvOptionSets.EndUpdate();

			if (trvOptionSets.SelectedNode == null) {
				// if we don't have a seleted node anymore, select the top one
				SelectNode(trvOptionSets.TopNode);
			} else if (SelectedNodePath != trvOptionSets.SelectedNode.FullPath) {
				// if we changed selected nodes then set the rest of the control to match the new node
				SelectNode(trvOptionSets.SelectedNode);
			}
		}

		/// <summary>
		/// Called to dispose of the editor.
		/// </summary>
		/// <param name="Disposing">True if we're being disposed.</param>
		protected override void Dispose(bool Disposing) {
			// unwire all the option sets from firing our handler
			if (Disposing) {
				WireEvents(_RootSet, false);
			}

			// pass it up the chain
			base.Dispose(Disposing);
		}

		/// <summary> 
		/// Initializes all of our controls.
		/// </summary>
		private void InitializeComponent() {
			// create the controls
			pnlTop              = new Panel();
			lblOptionLevel      = new Label();
			ddlOptionLevel      = new ComboBox();
			chbPropogate        = new CheckBox();
			splMain             = new SplitContainer();
			tlsOptionSetTools   = new ToolStrip();
			btnNew              = new ToolStripSplitButton();
			btnDelete           = new ToolStripButton();
			sep1                = new ToolStripSeparator();
			btnUp               = new ToolStripButton();
			btnDown             = new ToolStripButton();
			trvOptionSets       = new TreeView();
			lblOptionSet        = new Label();
			prgOptions          = new PropertyGrid();
			btnOK               = new Button();
			btnCancel           = new Button();
			btnApply            = new Button();

			// suspend layout until we're done setting up
			splMain.Panel1.SuspendLayout();
			splMain.Panel2.SuspendLayout();
			splMain.SuspendLayout();
			this.SuspendLayout();

			// pnlTop
			pnlTop.Dock     = DockStyle.Top;
			pnlTop.Name     = "pnlTop";
			pnlTop.Size     = new Size(0, 29);
			pnlTop.TabIndex = 0;
			pnlTop.Controls.Add(lblOptionLevel);
			pnlTop.Controls.Add(ddlOptionLevel);
			pnlTop.Controls.Add(chbPropogate);
			// lblOptionLevel
			lblOptionLevel.AutoSize = true;
			lblOptionLevel.Location = new Point(12, 11);
			lblOptionLevel.Name     = "lblOptionLevel";
			lblOptionLevel.Size     = new Size(70, 13);
			lblOptionLevel.TabIndex = 1;
			lblOptionLevel.Text     = "Option Level:";
			// ddlOptionLevel
			ddlOptionLevel.DropDownStyle         = ComboBoxStyle.DropDownList;
			ddlOptionLevel.FlatStyle             = FlatStyle.Flat;
			ddlOptionLevel.FormattingEnabled     = true;
			ddlOptionLevel.Location              = new Point(87, 8);
			ddlOptionLevel.Name                  = "ddlOptionLevel";
			ddlOptionLevel.Size                  = new Size(160, 21);
			ddlOptionLevel.TabIndex              = 2;
			ddlOptionLevel.SelectedIndexChanged += ddlOptionLevel_SelectedIndexChanged;
			// chbPropogate
			chbPropogate.AutoSize        = true;
			chbPropogate.Checked         = OptionDescriptor.PropogateChanges;
			chbPropogate.FlatStyle       = FlatStyle.Flat;
			chbPropogate.Location        = new Point(260, 10);
			chbPropogate.Name            = "chbPropogate";
			chbPropogate.Size            = new Size(232, 17);
			chbPropogate.TabIndex        = 3;
			chbPropogate.Text            = "Propogate Changes to Lower Option Levels";
			chbPropogate.CheckedChanged += chbPropogate_CheckedChanged;

			// splMain
			splMain.Anchor           = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
			splMain.Location         = new Point(8, 37);
			splMain.Name             = "splMain";
			splMain.Size             = new Size(484, 424);
			splMain.SplitterDistance = 175;
			splMain.TabIndex         = 1;
			// splMain.Panel1
			splMain.Panel1MinSize = 100;
			splMain.Panel1.Controls.Add(trvOptionSets);
			splMain.Panel1.Controls.Add(tlsOptionSetTools);
			// splMain.Panel2
			splMain.Panel2MinSize = 100;
			splMain.Panel2.Controls.Add(prgOptions);
			splMain.Panel2.Controls.Add(lblOptionSet);

			// pnlOptionSetTools
			tlsOptionSetTools.Name      = "tlsOptionSetTools";
			tlsOptionSetTools.GripStyle = ToolStripGripStyle.Hidden;
			tlsOptionSetTools.Visible   = false;
			tlsOptionSetTools.ImageList = new ImageList();
			tlsOptionSetTools.ImageList.Images.Add(Resources.NewFileIcon);
			tlsOptionSetTools.ImageList.Images.Add(Resources.DeleteFileIcon);
			tlsOptionSetTools.ImageList.Images.Add(Resources.UpArrowIcon);
			tlsOptionSetTools.ImageList.Images.Add(Resources.DownArrowIcon);
			tlsOptionSetTools.TabIndex  = 0;
			tlsOptionSetTools.Items.Add(btnNew);
			tlsOptionSetTools.Items.Add(btnDelete);
			tlsOptionSetTools.Items.Add(sep1);
			tlsOptionSetTools.Items.Add(btnUp);
			tlsOptionSetTools.Items.Add(btnDown);
			// btnNew
			btnNew.DisplayStyle = ToolStripItemDisplayStyle.Image;
			btnNew.DropDown     = new ToolStripDropDownMenu();
			btnNew.ImageIndex   = 0;
			btnNew.Name         = "btnNew";
			btnNew.Text         = "Add New Child OptionSet";
			btnNew.ButtonClick += btnNew_Click;
			// btnDelete
			btnDelete.DisplayStyle = ToolStripItemDisplayStyle.Image;
			btnDelete.ImageIndex   = 1;
			btnDelete.Name         = "btnDelete";
			btnDelete.Text         = "Remove Selected OptionSet";
			btnDelete.Click       += btnDelete_Click;
			// sep1
			sep1.Name = "sep1";
			// btnUp
			btnUp.DisplayStyle = ToolStripItemDisplayStyle.Image;
			btnUp.ImageIndex   = 2;
			btnUp.Name         = "btnUp";
			btnUp.Text         = "Move Selected OptionSet Up";
			btnUp.Click       += btnUp_Click;
			// btnDown
			btnDown.DisplayStyle = ToolStripItemDisplayStyle.Image;
			btnDown.ImageIndex   = 3;
			btnDown.Name         = "btnDown";
			btnDown.Text         = "Move Selected OptionSet Down";
			btnDown.Click       += btnDown_Click;
			// trvOptionSets
			trvOptionSets.Dock            = DockStyle.Fill;
			trvOptionSets.BorderStyle     = BorderStyle.FixedSingle;
			trvOptionSets.HideSelection   = false;
			trvOptionSets.Name            = "trvOptionSets";
			trvOptionSets.PathSeparator   = "/";
			trvOptionSets.TabIndex        = 1;
			trvOptionSets.NodeMouseClick += trvOptionSets_NodeMouseClick;

			// lblOptionSet
			lblOptionSet.BackColor = SystemColors.Highlight;
			lblOptionSet.Dock      = DockStyle.Top;
			lblOptionSet.Font      = new Font(Control.DefaultFont.FontFamily, 14, FontStyle.Bold);
			lblOptionSet.ForeColor = SystemColors.HighlightText;
			lblOptionSet.Name      = "lblOptionSet";
			lblOptionSet.Size      = new Size(0, 30);
			lblOptionSet.TabIndex  = 0;
			lblOptionSet.TextAlign = ContentAlignment.MiddleCenter;
			// prgOptions
			prgOptions.Dock     = DockStyle.Fill;
			prgOptions.Name     = "prgOptions";
			prgOptions.TabIndex = 1;

			// btnOK
			btnOK.Anchor   = AnchorStyles.Bottom | AnchorStyles.Right;
			btnOK.Location = new Point(251, 469);
			btnOK.Name     = "btnOK";
			btnOK.Size     = new Size(75, 23);
			btnOK.TabIndex = 1;
			btnOK.Text     = "OK";
			btnOK.Click   += btnOK_Click;
			// btnCancel
			btnCancel.Anchor   = AnchorStyles.Bottom | AnchorStyles.Right;
			btnCancel.Location = new Point(334, 469);
			btnCancel.Name     = "btnCancel";
			btnCancel.Size     = new Size(75, 23);
			btnCancel.TabIndex = 2;
			btnCancel.Text     = "Cancel";
			btnCancel.Click   += btnCancel_Click;
			// btnApply
			btnApply.Anchor   = AnchorStyles.Bottom | AnchorStyles.Right;
			btnApply.Enabled  = false;
			btnApply.Location = new Point(417, 469);
			btnApply.Name     = "btnApply";
			btnApply.Size     = new Size(75, 23);
			btnApply.TabIndex = 3;
			btnApply.Text     = "Apply";
			btnApply.Click   += btnApply_Click;

			// this
			this.Controls.Add(btnApply);
			this.Controls.Add(btnCancel);
			this.Controls.Add(btnOK);
			this.Controls.Add(splMain);
			this.Controls.Add(pnlTop);
			this.AcceptButton        = btnOK;
			this.AutoScaleDimensions = new SizeF(6F, 13F);
			this.AutoScaleMode       = AutoScaleMode.Font;
			this.CancelButton        = btnCancel;
			this.ClientSize          = new Size(500, 500);
			this.Name                = "OptionDialog";
			this.SizeGripStyle       = SizeGripStyle.Hide;
			this.Text                = "Options";
			this.Shown              += this_Shown;

			// resume layout now that we're done
			splMain.Panel1.ResumeLayout(false);
			splMain.Panel2.ResumeLayout(false);
			splMain.ResumeLayout(false);
			this.ResumeLayout(false);
		}

		/// <summary>
		/// Wires or unwires the events of the given OptionSet and all its children to fire our handler.
		/// </summary>
		/// <param name="OS">The OptionSet to wire/unwire.</param>
		/// <param name="Yes">True to wire the events, false to unwire them.</param>
		private void WireEvents(OptionSet OS, bool Yes) {
			// wire or unwire as appropriate
			if (Yes) {
				OS.OptionValueChanged          += OptionSet_OptionValueChanged;
				OS.OptionTemporaryValueChanged += OptionSet_OptionTemporaryValueChanged;
			} else {
				OS.OptionValueChanged          -= OptionSet_OptionValueChanged;
				OS.OptionTemporaryValueChanged -= OptionSet_OptionTemporaryValueChanged;
			}

			// do the same for each child
			foreach (OptionSet Child in OS.Children) {
				WireEvents(Child, Yes);
			}
		}

		/// <summary>
		/// Updates the Editor with the given Node as being selected.
		/// </summary>
		/// <param name="Node">The TreeNode that is selected.</param>
		private void SelectNode(TreeNode Node) {
			trvOptionSets.SelectedNode = Node;
			OptionSet OS = Node.Tag as OptionSet;
			lblOptionSet.Text = OS.Name;
			if (OS.Options.Count == 0) {
				prgOptions.Enabled = false;
				prgOptions.SelectedObject = null;
			} else {
				prgOptions.Enabled = true;
				prgOptions.SelectedObject = OS;
			}
		}

		/// <summary>
		/// Creates a TreeNode for the given OptionSet including sub-nodes for each of its children.
		/// </summary>
		/// <param name="OS">The OptionSet to create a node for.</param>
		/// <returns>A TreeNode that represents the given OptionSet.</returns>
		private TreeNode BuildNode(OptionSet OS) {
			TreeNode Node = new TreeNode(OS.Name);
			Node.Tag = OS;
			if (trvOptionSets.ImageList != null) {
				Node.ImageIndex = OS.IconIndex;
				Node.SelectedImageIndex = OS.SelectedIconIndex;
			}
			foreach (OptionSet Child in OS.GetSortedChildren(_Sorter)) {
				if ((_ShowEmpty || Child.HasOptions(true)) && (_ShowUninitialized || Child.AnyOptionsInitialized(true))) {
					Node.Nodes.Add(BuildNode(Child));
				}
			}
			return Node;
		}

		/// <summary>
		/// Refreshes the Enabled state of the Apply button.
		/// </summary>
		private void RefreshApplyEnabled() {
			if (!_ApplyingAll && _RootSet != null) {
				btnApply.Enabled = _RootSet.HasValueChanged(OptionDescriptor.CurrentValueIndex);
			}
		}

		/// <summary>
		/// Called whenever the form is shown.
		/// </summary>
		/// <param name="Sender">The Form that is being shown.</param>
		/// <param name="e">No arguments.</param>
		private void this_Shown(object Sender, EventArgs e) {
			RefreshDisplay();
		}

		/// <summary>
		/// Called when the selected index of the option level list is changed.
		/// </summary>
		/// <param name="Sender">The ComboBox whose selected index changed.</param>
		/// <param name="e">No arguments.</param>
		private void ddlOptionLevel_SelectedIndexChanged(object Sender, EventArgs e) {
			_InitialValueIndex = _NameIndices[ddlOptionLevel.SelectedItem as string];
			OptionDescriptor.CurrentValueIndex = _InitialValueIndex;
			prgOptions.Refresh();
			RefreshApplyEnabled();
		}

		/// <summary>
		/// Called when the checked state of the propogate values checkbox is changed.
		/// </summary>
		/// <param name="Sender">The CheckBox whose checked state changed.</param>
		/// <param name="e">No arguments.</param>
		private void chbPropogate_CheckedChanged(object Sender, EventArgs e) {
			OptionDescriptor.PropogateChanges = chbPropogate.Checked;
		}

		/// <summary>
		/// Called when the New button or one of its options is clicked.
		/// </summary>
		/// <param name="Sender">The button or menu item that was clicked.</param>
		/// <param name="e">No arguments.</param>
		private void btnNew_Click(object Sender, EventArgs e) {
			// grab the base option set that was clicked and clone it
			OptionSet BaseOS = (Sender as ToolStripItem).Tag as OptionSet;

			// grab the selected node and its option set
			TreeNode SelectedNode = trvOptionSets.SelectedNode;
			OptionSet SelectedOS   = SelectedNode.Tag as OptionSet;

			// make sure the selected option set doesn't already have a set with the same name
			if (SelectedOS.HasOptionSet(BaseOS.Name)) {
				MessageBox.Show(this, "Unable to add the item because another item already exists at this level with the same name.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return;
			}

			// clone the base option set
			OptionSet NewOS = BaseOS.Clone() as OptionSet;

			// create a new tree node out of it
			TreeNode NewNode = new TreeNode(NewOS.Name, NewOS.IconIndex, NewOS.SelectedIconIndex);
			NewNode.Tag = NewOS;

			// add the new node under the selected node
			SelectedNode.Nodes.Add(NewNode);
			SelectedNode.ExpandAll();

			// add the new option set under the selected one
			SelectedOS.AddOptionSet(NewOS);

			// set the new option set's order to be at the end of its set
			NewOS.OrderIndex = SelectedNode.Nodes.Count;

			// wire the new set to fire our events
			WireEvents(NewOS, true);

			// set the new node as the selected one
			SelectNode(NewNode);

			// refresh the tree view
			trvOptionSets.Refresh();
		}

		/// <summary>
		/// Called when the Delete button is clicked.
		/// </summary>
		/// <param name="Sender">The button that was clicked.</param>
		/// <param name="e">No arguments.</param>
		private void btnDelete_Click(object Sender, EventArgs e) {
			// if this is the parent node, don't bother
			if (trvOptionSets.SelectedNode == trvOptionSets.TopNode) {
				return;
			}

			// find the selected node and its parent
			TreeNode SelectedNode = trvOptionSets.SelectedNode;
			TreeNode ParentNode = SelectedNode.Parent;

			// make sure they really want to do this
			DialogResult DR = MessageBox.Show(this, "Are you sure you want to permanently remove the '" + SelectedNode.Text + "' option from the menu?", "Are you sure?", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
			if (DR != DialogResult.Yes) {
				return;
			}

			// grab the selected option set and its parent
			OptionSet SelectedOS = SelectedNode.Tag as OptionSet;
			OptionSet ParentOS   = ParentNode.Tag as OptionSet;

			// remove the selected option set from the parent option set
			ParentOS.RemoveOptionSet(SelectedOS.Name);

			// reset the ordering on the rest of the sets
			int Index = 1;
			foreach (OptionSet OS in ParentOS.GetSortedChildren(_Sorter)) {
				OS.OrderIndex = Index;
				++Index;
			}

			// remove the selected node from the tree
			trvOptionSets.Nodes.Remove(SelectedNode);

			// select the parent node
			SelectNode(ParentNode);
		}

		/// <summary>
		/// Called when the Up button is clicked.
		/// </summary>
		/// <param name="Sender">The button that was clicked.</param>
		/// <param name="e">No arguments.</param>
		private void btnUp_Click(object Sender, EventArgs e) {
			// grab the two nodes we'll be switching
			TreeNode SelectedNode = trvOptionSets.SelectedNode;
			TreeNode PrevNode     = SelectedNode.PrevNode;
			if (PrevNode == null) {
				return;
			}

			// grab their option sets
			OptionSet SelectedOS = SelectedNode.Tag as OptionSet;
			OptionSet PrevOS     = PrevNode.Tag as OptionSet;

			// swap their order indices
			int Temp = SelectedOS.OrderIndex;
			SelectedOS.OrderIndex = PrevOS.OrderIndex;
			PrevOS.OrderIndex = Temp;

			// re-build the list
			RefreshDisplay();
		}

		/// <summary>
		/// Called when the Down button is clicked.
		/// </summary>
		/// <param name="Sender">The button that was clicked.</param>
		/// <param name="e">No arguments.</param>
		private void btnDown_Click(object Sender, EventArgs e) {
			// grab the two nodes we'll be switching
			TreeNode SelectedNode = trvOptionSets.SelectedNode;
			TreeNode NextNode     = SelectedNode.NextNode;
			if (NextNode == null) {
				return;
			}

			// grab their option sets
			OptionSet SelectedOS = SelectedNode.Tag as OptionSet;
			OptionSet NextOS     = NextNode.Tag as OptionSet;

			// swap their order indices
			int Temp = SelectedOS.OrderIndex;
			SelectedOS.OrderIndex = NextOS.OrderIndex;
			NextOS.OrderIndex = Temp;

			// re-build the list
			RefreshDisplay();
		}

		/// <summary>
		/// Called when a node in the tree view is clicked.
		/// </summary>
		/// <param name="Sender">The tree view that a node was clicked on.</param>
		/// <param name="e">Arguments about the node clicked.</param>
		private void trvOptionSets_NodeMouseClick(object Sender, TreeNodeMouseClickEventArgs e) {
			SelectNode(e.Node);
		}

		/// <summary>
		/// Called whenever the value of an Option in the dialog changes.
		/// </summary>
		/// <param name="Sender">The Option whose value changed.</param>
		/// <param name="e">Arguments about the change in value.</param>
		private void OptionSet_OptionValueChanged(object Sender, OptionValueChangedEventArgs e) {
			RefreshApplyEnabled();
		}

		/// <summary>
		/// Called whenever the temporary value of an Option in the dialog changes.
		/// </summary>
		/// <param name="Sender">The Option whose temporary value changed.</param>
		/// <param name="e">Arguments about the change in value.</param>
		private void OptionSet_OptionTemporaryValueChanged(object Sender, OptionValueChangedEventArgs e) {
			RefreshApplyEnabled();
		}

		/// <summary>
		/// Called when the OK button is clicked.
		/// </summary>
		/// <param name="Sender">The button that was clicked.</param>
		/// <param name="e">No arguments.</param>
		private void btnOK_Click(object Sender, EventArgs e) {
			ApplyAllNewValues();
			this.DialogResult = DialogResult.OK;
			Close();
		}

		/// <summary>
		/// Called when the Cancel button is clicked.
		/// </summary>
		/// <param name="Sender">The button that was clicked.</param>
		/// <param name="e">No arguments.</param>
		private void btnCancel_Click(object Sender, EventArgs e) {
			CancelAllNewValues();
			this.DialogResult = DialogResult.Cancel;
			Close();
		}

		/// <summary>
		/// Called when the Apply button is clicked.
		/// </summary>
		/// <param name="Sender">The button that was clicked.</param>
		/// <param name="e">No arguments.</param>
		private void btnApply_Click(object Sender, EventArgs e) {
			ApplyAllNewValues();
		}
	}
}